from argparse import ArgumentParser, Namespace
from typing import List
import git.exc
from west.commands import WestCommand
import west
from west import log
from textwrap import dedent
import os
from enum import Enum
import west.commands
import west.manifest
import west.util
import yaml
from pathlib import Path
import shutil
from jinja2 import Environment, FileSystemLoader
import git
from git import Repo
copyright = "Copyright (c) Sonosemi Medical Ltd.\n"
project_path = Path(west.util.west_topdir())
manifest_path = west.manifest.manifest_path()
module_path=f"{project_path}/modules"
current_path=os.path.dirname(__file__)

def GenerateFileFromTemplate(template_file_name: str, path: str, generated_file_name: str, **kwargs):
    try:
        env = Environment(loader=FileSystemLoader(current_path))
        template = env.get_template(template_file_name)
        rendered_content = template.render(**kwargs)
        with open(os.path.join(path,generated_file_name), 'w') as f:
            f.write(rendered_content)
        print(f"generated {generated_file_name}")
    except Exception as e:
        print(f"error happens when generating {generated_file_name}: {e}")

class Driver:
    def __init__(self, name:str, deps):
        self.deps = deps
        self.name = name
        self.upper_name = name.upper()
        self.path = os.path.join(module_path, name)
        if not os.path.exists(module_path):
            os.mkdir(module_path)
        os.mkdir(self.path)
        self.doc_path = os.path.join(self.path, "doc")
        os.mkdir(self.doc_path)
        self.src_path=os.path.join(self.path, "src")
        os.mkdir(self.src_path)
        self.module_file_path=os.path.join(self.path, "zephyr")
        os.mkdir(self.module_file_path)
        self.bindings_path = f"{self.path}/dts/bindings"
        os.makedirs(self.bindings_path)
        self.include_path = os.path.join(self.path, "include")
        self.public_inc_path = os.path.join(os.path.join(self.include_path, "public"), self.name)
        self.internal_inc_path = os.path.join(os.path.join(self.include_path, "internal"), self.name)
        self.syscall_header_path = f"{self.include_path}/zephyr/drivers"
        os.makedirs(self.syscall_header_path)
        os.makedirs(self.public_inc_path)
        os.makedirs(self.internal_inc_path)
        open(os.path.join(self.path, "README.md"), 'w')
        self.GenerateCmake()
        self.GenerateCHeader()
        self.GenerateModuleFile()
        self.GenerateKconfig()
        self.GenerateTests()
        self.InitGit()
    def GenerateCHeader(self):
        GenerateFileFromTemplate("zephyr_driver.h.in", 
                                 self.syscall_header_path, 
                                 f"{self.name}.h", 
                                 driver=self.name, 
                                 DRIVER=self.name.upper())

    def GenerateModuleFile(self):
        module_data = {
            "name":self.name,
            "build": {
                "depends": self.deps,
                "cmake": ".",
                "kconfig": "Kconfig",
                "settings": {
                    "dts_root": "."
                }
            }
        }
        with open(os.path.join(self.module_file_path, "module.yaml"), "w") as f:
            data = yaml.safe_dump(module_data, f)
    def GenerateCmake(self):
        GenerateFileFromTemplate("driver_CMakeLists.txt.in", 
                                 self.path, 
                                 "CMakeLists.txt", 
                                 module=self.name, 
                                 MODULE=self.name.upper())
    def GenerateKconfig(self):
        template_file = os.path.join(current_path, "Kconfig.in")
        GenerateFileFromTemplate("Kconfig.in", 
                                 self.path, 
                                 "Kconfig", 
                                 driver=self.name, 
                                 DRIVER=self.name.upper())

    def GenerateTests(self):
        tests_dir = os.path.join(self.path, "tests")
        os.mkdir(tests_dir)
        board_dir = os.path.join(tests_dir, "boards")
        os.mkdir(board_dir)
        with open(os.path.join(board_dir, f"test_{self.name}.overlay"), "w") as f:
            f.write("/ {\r\n\r\n};")
        GenerateFileFromTemplate("test.cpp.in", self.src_path, f"test_{self.name}.cpp", test_suite=self.name, driver=self.name) 
        GenerateFileFromTemplate("test_prj.conf.in", tests_dir, "prj.conf", MODULE=self.name.upper())
        testcase_data = {
            "tests":{
                f"{self.name}_driver.utest":{
                    "platform_allow" : [
                        "native_sim/native/64",
                        "qemu_cortex_m3",
                        "qemu_cortex_m0",
                    ],
                    "harness": "ztest",
                    "extra_args": f"DTC_OVERLAY_FILE=boards/test_{self.name}.overlay"
                }
            }
        }
        with open(os.path.join(tests_dir, "testcase.yaml"), "w") as f:
            yaml.safe_dump(testcase_data, f)
    def InitGit(self):
        try:
            repo = git.Repo.init(self.path)
        except git.exc.GitError:
            print(f"Failed to initialized a git repository at {self.path}")
class CommentType(Enum):
    script = "#" 
    c_source = "//"

def FileAddCopyright(file: str, type: CommentType): 
    file.write(type.value + copyright)

def RemoveModuleFromManifest(module:str):
    try:
        with open(manifest_path, 'r') as f:
            data = yaml.safe_load(f)

        projects = data.get('manifest', {}).get('projects', [])
        new_projects = []
        for project in projects:
            if project.get('name')!= module:
                new_projects.append(project)

        if 'manifest' in data:
            data['manifest']['projects'] = new_projects
        else:
            data['manifest'] = {'projects': new_projects}

        with open(manifest_path, 'w') as f:
            yaml.safe_dump(data, f)
            print(f"Successfully removed module {module} from the manifest.")
    except FileNotFoundError:
        print("west.yml file not found.")
    except yaml.YAMLError as e:
        print(f"Error while handling YAML: {e}")

def AddModuleFromManifest(module: str, url="undefined", rversion="main"):
    try:
        with open(manifest_path, 'r') as f:
            manifest = yaml.safe_load(f)

        projects = manifest.get('manifest', {}).get('projects', [])
        new_project = {
            "name": module,
            "path": f"modules/{module}",
            "revision": rversion,
            "url": url
        }
        projects.append(new_project)
        if 'manifest' in manifest:
            manifest['manifest']['projects'] = projects
        else:
            manifest['manifest'] = {'projects': projects}

        with open(manifest_path, 'w') as f:
            yaml.safe_dump(manifest, f)

        print(f"Successfully added module {module} to the west.yml manifest.")
    except FileNotFoundError:
        print("west.yml file not found.")
    except yaml.YAMLError as e:
        print(f"Error while handling YAML: {e}")

def RemoveModule(name: str):
    item_path = os.path.join(module_path, name)
    try:
        shutil.rmtree(item_path)
    except FileNotFoundError:
        pass
    RemoveModuleFromManifest(name)

def AddDriver(name: str, url: str, deps):
    driver_path = os.path.join(module_path, name)
    try:
        driver = Driver(name, deps)
        AddModuleFromManifest(name, url)
    except FileExistsError:
        print(f"ERROR: The module path {driver_path} already exists.")
def AddLibrary(name:str, url:str,deps):
    AddDriver(name, url, deps)
    lib_path = os.path.join(module_path, name)    
    driver_inc = os.path.join(lib_path, "include/zephyr")
    dts_inc = os.path.join(lib_path, "dts")
    shutil.rmtree(driver_inc)
    shutil.rmtree(dts_inc)
    os.remove(os.path.join(lib_path, "CMakeLists.txt"))
    os.remove(os.path.join(lib_path, "Kconfig"))
    GenerateFileFromTemplate("Kconfig_lib.in", lib_path, "Kconfig", LIB=name.upper(), lib=name)
    GenerateFileFromTemplate("CMakeLists.txt_lib.in", lib_path, "CMakeLists.txt", LIB=name.upper(), lib=name)
def AddModule(name:str, url_path:str, type:str, deps):
    if type == "driver":
        AddDriver(name, url_path)
    elif type == "library":
        AddLibrary(name, url_path)
    else:
        #TODO
        pass
def Vendor(qualifier:str):
    if "stm32" in qualifier:
        return "st"
    elif "nrf" in qualifier:
        return "nordic"
    elif "mimxrt" in qualifier:
        return "nxp"
    else:
        return "vendor"
def CloneAndCheckout(repo_url:str, local_dir:str, revision:str):
    try:
        repo = Repo.clone_from(repo_url, local_dir)
        print(f"repository cloned at {local_dir}")
        repo.git.checkout(revision)
        print(f"Checked revision: {revision}")
    except:
        print(f"failed to clone {repo_url}")
        
def AddBoard(name:str, qualifier:str, url_path:str):
    board_path = os.path.join(os.path.join(project_path, "boards"), name)
    os.makedirs(board_path)
    doc_path = os.path.join(board_path, "doc")
    os.makedirs(doc_path)
    board_target = f"{name}_{qualifier}"
    vendor = Vendor(qualifier)
    GenerateFileFromTemplate("board_CMakeLists.txt.in", board_path, "CMakeLists.txt", board=name)
    GenerateFileFromTemplate("board_defconfig.in", board_path, f"{name}_defconfig", board=name)
    GenerateFileFromTemplate("board_Kconfig.defconfig.in", board_path, f"Kconfig.defconfig", board=name, BOARD=name.upper())
    GenerateFileFromTemplate("board_name.yml.in", board_path, "board.yml", board=name, target=qualifier, vendor=vendor)
    GenerateFileFromTemplate("board.yaml.in", board_path, f"{name}.yml", board=board_target, target=qualifier, vendor=vendor)
    GenerateFileFromTemplate("board.cmake.in", board_path, "board.cmake", board=board_target)
    GenerateFileFromTemplate("board.dts.in", board_path, f"{name}.dts",board=board_target,vendor=vendor)
    GenerateFileFromTemplate("Kconfig.board.in", board_path, f"Kconfig.{name}",BOARD=name.upper(),MCU=qualifier.upper())
def AddZephyrModule(module_name:str, url_repo:str):
    zephyr_path = os.path.join(project_path, "extern/zephyr")
    revision = ""
    path = ""

    with open(os.path.join(zephyr_path, "west.yml"),mode="r") as f:
        west_yml = yaml.safe_load(f)
        module_list = west_yml.get("manifest", {}).get("projects",[])
        for module in module_list:
            if module.get("name") == module_name:
                revision = module.get("revision")
                path = module.get("path")
                print(f"Found module {module_name} {revision} ") 
    allocation = os.path.join(f"{project_path}/extern/", path)
    if not os.path.exists(allocation):
        print(f"Downling into {allocation}")
        CloneAndCheckout(url_repo, allocation, revision)
        print("Downloaded successfully")
    else:
        print(f"{module_name} already exits")
    with open(manifest_path, mode="r") as f:
        west_yml = yaml.safe_load(f)
        prj_list = west_yml.get("manifest", {}).get("projects",[])
        for prj in prj_list:
            if "import" in prj:
                if "name-allowlist" in prj["import"] and not module_name in prj["import"]["name-allowlist"]:
                    prj["import"]["name-allowlist"].append(module_name)
                    with open(manifest_path, mode="w") as f:
                        yaml.dump(west_yml,f)
                        print(f"Registered {module_name} into west.yml")
                else:
                    print(f"{module_name} already resigiered")
def AddThirdPartyModule(module_name:str, url:str, revision:str):
    module_path = os.path.join(project_path, f"modules/{module_name}")
    if not os.path.exists(module_path):
        CloneAndCheckout(url, module_path, revision)
        AddModuleFromManifest(module_name, url, revision)
        print("Downloaded successfully")
    else:
        print(f"{module_name} already exits")
def GetNameFromGitUrl(git_url:str):
    if git_url.endswith(".git"):
        git_url = git_url[:-4]
    
    parts = git_url.split(":")[-1].split("/")
    return parts[-1]

class ModuleCommand(WestCommand):
    def __init__(self):
        super().__init__(
            name="module",
            help="operate modules in the project",
            description='''
            "operate modules in the project"
            ''',
            accepts_unknown_args=False
        )

    def do_add_parser(self, parser_adder) -> ArgumentParser:
        parser = parser_adder.add_parser(self.name, 
                                         help = self.help,
                                        description = self.description)
        parser.add_argument("-a", "--add", help="add a module in the project")
        parser.add_argument("-t", "--type", help="module type", choices=["driver", "library", "board"])
        parser.add_argument("-d", "--delete", help="remove a module from this project")
        parser.add_argument("-u", "--url", help="specify a url address for the module to add", default="undefined")
        parser.add_argument("--deps", help="dependencies of the module to add", default=[], action="append")
        parser.add_argument("-q", "--qualifier", help="a description of SOC, eg nrf5340, stm32f429xx ", default="")
        parser.add_argument("-z", "--zmodule", help="update a remote zephyr module from sonosmi gitlab")
        parser.add_argument("-p", "--pull", help="pull a remote third party module from sonosmi gitlab")
        parser.add_argument("-r", "--revision", help="specify a revision for pulling a remote module from the gitlab")
        return parser
    def do_run(self, args: Namespace, unknown: List[str]):
        module_path = f"{project_path}/modules"
        if not args.add is None:
            if args.type == "driver":
                AddDriver(name=args.add, url=args.url, deps=args.deps)
            elif args.type == "library":
                AddLibrary(name=args.add, url=args.url, deps=args.deps)
            elif args.type == "board":
                if args.qualifier == "":
                    self.parser.error("Must specify a mcu qualifier")
                else:
                    AddBoard(name=args.add, qualifier=args.qualifier, url_path=args.url)
            else:
                self.parser.error(f"Must specify a type when adding a module")
                self.parser.print_usage()                
                raise west.commands.CommandError
        elif not args.delete is None:
            print(f"Deleting module {args.delete}")
            RemoveModule(name=args.delete)
        elif not args.zmodule is None:
            print(f"Add a zephyr module from the gitlab")
            if args.url == "undefined":
                self.parser.error("The url of this module at gitlab must be specified")
            AddZephyrModule(args.zmodule, args.url)
        elif not args.pull is None:
            print("Pull a third party module from the gitlab")
            if args.url == "undefined":
                self.parser.error("The url of this module at gitlab must be specified")
            AddThirdPartyModule(args.pull, args.url, args.revision)    
     